#include "hook.h"

#include <dlfcn.h>
#include <functional>
#include <unistd.h>
#include "log.h"
#include "iomanger.h"
#include "fd_manager.h"
#include "util.h"
#include "config.h"

sylar::Logger::LoggerPtr g_logger = SYLAR_LOG_NAME("system");

namespace sylar
{

static sylar::ConfigVar<int>::ptr g_tcp_connect_timeout = sylar::Config::LookUp("tcp.connect.timeout", 500, "tcp connect timeout");

static thread_local bool t_hook_enable = false;

// 定义hook函数宏
#define HOOK_FUN(XX)    \
        XX(sleep)       \
        XX(usleep)      \
        XX(nanosleep)   \
        XX(socket)      \
        XX(connect)     \
        XX(accept)      \
        XX(read)        \
        XX(readv)       \
        XX(recv)        \
        XX(recvmsg)    \
        XX(recvfrom)    \
        XX(write)       \
        XX(writev)      \
        XX(sendto)      \
        XX(send)        \
        XX(sendmsg)     \
        XX(close)       \
        XX(fcntl)       \
        XX(setsockopt)  \
        XX(getsockopt)  \
        XX(ioctl)

void hook_init()
{
    static bool is_init = false;
    if (is_init)
    {
        return;
    }

#define XX(name) name ## _f = (name ## _fun)dlsym(RTLD_NEXT,  #name);
    HOOK_FUN(XX)
#undef XX

}

static uint64_t s_connect_timeout = -1;

struct _HookIniter
{
    _HookIniter()
    {
        hook_init();
        s_connect_timeout = g_tcp_connect_timeout->getValue();
        g_tcp_connect_timeout->addListener([](const int& old_value, const int new_val){
            SYLAR_LOG_INFO(g_logger) << "tcp connect timeout changed from " << old_value << " to " << new_val;
            s_connect_timeout = new_val;
        });
    }
};

static _HookIniter _s_hook_init;


bool is_hook_enable()
{
    return t_hook_enable;
}

void set_hook_enable(bool flag)
{
    t_hook_enable = flag;
}




}


struct timer_info
{
    int cancelled = 0;

};


/// @brief io相关的操作
/// @tparam OriginFun hook中原始的函数
/// @tparam ...Args 可变参数类型
/// @param fd 
/// @param fun 
/// @param hook_fun_name hool的函数名
/// @param event 事件
/// @param timeout_so 超时类型
/// @param buflen 
/// @param ...args 
/// @return 
template<typename OriginFun, typename... Args>
static ssize_t do_io(int fd, 
                OriginFun fun, 
                const char* hook_fun_name, 
                uint32_t event, 
                int timeout_so, 
                Args&&... args)
{
    if (!sylar::t_hook_enable)
    {
        return fun(fd, std::forward<Args>(args)...);
    }

    SYLAR_LOG_DEBUG(g_logger) << "do_io " << hook_fun_name;
    sylar::FdCtx::ptr ctx = sylar::FdMgr::GetInstance()->get(fd);
    if(!ctx)
    {   // 不是socket
        return fun(fd, std::forward<Args>(args)...);
    }

    if(ctx->isClose())
    {
        errno = EBADF;
        return -1;
    }

    if (!ctx->isScoket() || ctx->getUserNoBlock())
    {
        return fun(fd, std::forward<Args>(args)...);
    }

    uint64_t to = ctx->getTimeOut(timeout_so);
    std::shared_ptr<timer_info> tinfo(new timer_info);

retry:

    ssize_t n = fun(fd, std::forward<Args>(args)...);
    // 系统中断
    while(n == -1 && errno == EINTR)
    {
        n = fun(fd, std::forward<Args>(args)...);
    }
    // n = -1;
    // errno = EAGAIN;
    // 没有数据 
    if (n == -1 && errno == EAGAIN)
    {
        sylar::IOManger* iom = sylar::IOManger::GetThis();
        sylar::Timer::ptr timer;
        SYLAR_LOG_DEBUG(g_logger) << "do_io 1 " << hook_fun_name;
        std::weak_ptr<timer_info> winfo(tinfo);
        if(to != (uint64_t)-1)
        {
            // 添加条件超时定时器
            timer = iom->addConditionTimer(to, [winfo, fd, iom, event](){
                auto t = winfo.lock();
                if(!t || t->cancelled)
                {
                    return;
                }
                t->cancelled = ETIMEDOUT;
                iom->cancelEvent(fd, (sylar::IOManger::Event)(event));
            }, winfo);
        }

        // 添加一个事件
        int ret = iom->addEvent(fd, (sylar::IOManger::Event)(event));
        if (ret)
        {

            SYLAR_LOG_ERROR(g_logger) << hook_fun_name << " addEvent( " << fd << ", " << event << ")";
            if(timer)
            {
                timer->cancel();
            }
            return -1;
        }
        else
        {
            // 两个唤醒，event, time
            SYLAR_LOG_DEBUG(g_logger) << "do_io 2 " << hook_fun_name;
            sylar::Fiber::YaildToHold();
            SYLAR_LOG_DEBUG(g_logger) << "do_io 3 " << hook_fun_name;
            if(timer)
            {
                timer->cancel();
            }
            if (tinfo->cancelled)
            {
                errno = tinfo->cancelled;
                return -1;
            }

            goto retry;
        }
    }

    return n;
}

extern "C"
{

// 声明
#define XX(name) name ## _fun name ##_f = NULL;
    HOOK_FUN(XX)
#undef XX


typedef void(sylar::IOManger::* IOM_SCHEDULE_PTR)(sylar::Fiber::ptr, int);

// 自己实现的hook
unsigned int sleep(unsigned int seconds)
{
    // 
    if (!sylar::t_hook_enable)
    {
        return sleep_f(seconds);
    }

    sylar::Fiber::ptr fiber = sylar::Fiber::GetThis();
    sylar::IOManger* iom = sylar::IOManger::GetThis();
    if (nullptr == iom)
    {
        return sleep_f(seconds);
    }
    // iom->addTimer(usec / 1000, std::bind(&sylar::IOManger::scheduler, iom, fiber));
    // iom->addTimer(seconds * 1000, [fiber, iom]() {
    //     iom->scheduler(fiber);
    // });

    iom->addTimer(seconds * 1000, 
        std::bind((IOM_SCHEDULE_PTR)&sylar::IOManger::schedule, iom, fiber, -1) );

    sylar::Fiber::YaildToHold();
    return 0;
}   

int usleep(useconds_t usec)
{
    // 
    if (!sylar::t_hook_enable)
    {
        return usleep_f(usec);
    }

    sylar::Fiber::ptr fiber = sylar::Fiber::GetThis();
    sylar::IOManger* iom = sylar::IOManger::GetThis();
    if (nullptr == iom)
    {
        return usleep_f(usec);
    }
    //iom->addTimer(usec / 1000, std::bind(&sylar::IOManger::scheduler, iom, fiber));
    // iom->addTimer((uint64_t)usec * 1000, [fiber, iom]() {
    //     iom->scheduler(fiber);
    // });

    iom->addTimer((uint64_t)usec * 1000, 
            std::bind((IOM_SCHEDULE_PTR)&sylar::IOManger::schedule,  iom, fiber, -1));
    sylar::Fiber::YaildToHold();
    return 0;
}


int nanosleep(const struct timespec *req, struct timespec *rem)
{
    if (!sylar::t_hook_enable)
    {
        return nanosleep_f(req, rem);
    }

    uint64_t timeout_ms = req->tv_sec * 100 + req->tv_nsec / 1000 / 1000;
    sylar::Fiber::ptr fiber = sylar::Fiber::GetThis();
    sylar::IOManger* iom = sylar::IOManger::GetThis();
    if (nullptr == iom)
    {
        nanosleep_f(req, rem);
    }
    //iom->addTimer(usec / 1000, std::bind(&sylar::IOManger::scheduler, iom, fiber));
    // iom->addTimer((uint64_t)timeout_ms, [fiber, iom]() {
    //     iom->schedule(fiber);
    // });
    iom->addTimer((uint64_t)timeout_ms, 
            std::bind((IOM_SCHEDULE_PTR)&sylar::IOManger::schedule,  iom, fiber, -1));
    sylar::Fiber::YaildToHold();
    return 0;
}

int socket(int domain, int type, int protocol)
{
    if (!sylar::t_hook_enable)
    {
        return socket_f(domain, type, protocol);
    }

    int fd = socket_f(domain, type, protocol);
    if (fd == -1)
    {
        return fd;
    }
    sylar::FdMgr::GetInstance()->get(fd, true);

    return fd;
}


int connect_with_timeout(int fd, const struct sockaddr* addr, socklen_t addrlen, uint64_t timeout_ms)
{
    if (!sylar::t_hook_enable)
    {
        return connect_f(fd, addr, addrlen);
    }

    sylar::FdCtx::ptr ctx = sylar::FdMgr::GetInstance()->get(fd);
    if (!ctx || ctx->isClose())
    {
        errno = EBADF;
        return -1;
    }

    if (!ctx->isScoket())
    {
        return connect_f(fd, addr, addrlen);
    }

    if (ctx->getUserNoBlock())
    {
        return connect_f(fd, addr, addrlen);
    }

    int n = connect_f(fd, addr, addrlen);
    if (n == 0)
    {
        return 0;
    }
    else if (n != -1 || errno != EINPROGRESS)
    {
        return n;
    }

    sylar::IOManger* iom = sylar::IOManger::GetThis();
    sylar::Timer::ptr timer;

    std::shared_ptr<timer_info> tinfo(new timer_info);
    std::weak_ptr<timer_info> winfo(tinfo);

    if (timeout_ms != (uint64_t)-1)
    {   // 定时
        timer = iom->addConditionTimer(timeout_ms, [winfo, fd, iom](){
            auto t = winfo.lock();
            if (!t || t->cancelled)
            {
                return;
            }
            t->cancelled = ETIMEDOUT;
            iom->cancelEvent(fd, sylar::IOManger::WRITE);
        }, winfo);
    }

    // 添加事件
    int ret = iom->addEvent(fd, sylar::IOManger::WRITE);
    if (ret == 0)
    {
        sylar::Fiber::YaildToHold();
        if (timer)
        {
            timer->cancel();
        }
        if (tinfo->cancelled)
        {
            errno = tinfo->cancelled;
            return -1;
        }

    }
    else
    {
        if (timer)
        {
            timer->cancel();
        }
        SYLAR_LOG_ERROR(g_logger) << "connect addEvent(" << fd << ", WEITE) ERROR";
    }

    int error = 0;
    socklen_t len = sizeof(int);
    if(-1 == getsockopt(fd, SOL_SOCKET, SO_ERROR, &errno, &len))
    {
        return -1;
    }
    if (!error)
    {
        return 0;
    }
    else
    {
        errno = error;
        return -1;
    }
}

int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen)
{
    return connect_with_timeout(sockfd, addr, addrlen, sylar::s_connect_timeout);  
}


int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen)
{
    int fd = do_io(sockfd, accept_f, "accept", sylar::IOManger::READ, SO_RCVTIMEO, addr, addrlen);
    if (fd >= 0)
    {
        sylar::FdMgr::GetInstance()->get(fd, true);
    }
    return fd;
}

ssize_t read(int fildes, void *buf, size_t nbyte)
{
    return do_io(fildes, read_f, "read", sylar::IOManger::READ, SO_RCVTIMEO, buf, nbyte);
}


ssize_t readv(int fd, const struct iovec *iov, int iovcnt)
{
    return do_io(fd, readv_f, "readv", sylar::IOManger::READ, SO_RCVTIMEO, iov, iovcnt);
}



ssize_t recv(int sockfd, void *buf, size_t len, int flags)
{
    return do_io(sockfd, recv_f, "recv", sylar::IOManger::READ, SO_RCVTIMEO,  buf,len, flags);
}


ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags,
                        struct sockaddr *src_addr, socklen_t *addrlen)
{
    return do_io(sockfd, recvfrom_f, "recvfrom", sylar::IOManger::READ, SO_RCVTIMEO, buf, len, flags, src_addr, addrlen);
}


ssize_t recvmsg(int sockfd, struct msghdr *msg, int flags)
{
    return do_io(sockfd, recvmsg_f, "recvmsg" ,sylar::IOManger::READ, SO_RCVTIMEO, msg, flags);
}



ssize_t write(int fildes, const void *buf, size_t nbyte)
{
    return do_io(fildes, write_f, "write", sylar::IOManger::WRITE, SO_SNDTIMEO, buf, nbyte);
}



ssize_t writev(int fd, const struct iovec *iov, int iovcnt)
{
    return do_io(fd, writev_f, "writev", sylar::IOManger::WRITE, SO_SNDTIMEO, iov, iovcnt);
}



ssize_t send(int sockfd, const void *buf, size_t len, int flags)
{
    return do_io(sockfd, send_f, "send", sylar::IOManger::WRITE, SO_SNDTIMEO, buf, len, flags);
}



ssize_t sendto(int sockfd, const void *buf, size_t len, int flags,
                      const struct sockaddr *dest_addr, socklen_t addrlen)
{
    return do_io(sockfd, sendto_f, "sendto", sylar::IOManger::WRITE, SO_SNDTIMEO, buf, len, flags, dest_addr,addrlen);
}


ssize_t sendmsg(int sockfd, const struct msghdr *msg, int flags)
{
    return do_io(sockfd, sendmsg_f, "sendmsg", sylar::IOManger::WRITE, SO_SNDTIMEO, msg, flags);
}

int close(int fd)
{
    if (sylar::t_hook_enable)
    {
        return close_f(fd);
    }

    sylar::FdCtx::ptr ptr_ctx = sylar::FdMgr::GetInstance()->get(fd);

    if (ptr_ctx)
    {
        auto iom = sylar::IOManger::GetThis();
        if(iom)
        {
            iom->cancelAll(fd);
        }
        sylar::FdMgr::GetInstance()->del(fd);
    }
    return close_f(fd);
}


// #include <fcntl.h>
int fcntl(int fd, int cmd, ... /* arg */ )
{
    // if (!sylar::t_hook_enable)
    // {
    //     return fcntl_f(fd, cmd,  );
    // }
    
    va_list va;
    va_start(va, cmd);
    
    switch (cmd)
    {
        /*int 类型*/
    case F_SETFL:
    {
        int arg = va_arg(va, int);
        va_end(va);
        sylar::FdCtx::ptr ctx = sylar::FdMgr::GetInstance()->get(fd);
        if (!ctx || ctx->isClose() || !ctx->isScoket())
        {
            return fcntl_f(fd, cmd, arg);
        } 
        ctx->setUserNoBlock(arg & O_NONBLOCK);
        if (ctx->getSysNoBlock())
        {
            arg |= O_NONBLOCK;
        }
        else
        {
            arg &= ~O_NONBLOCK;
        }
        return fcntl_f(fd, cmd, arg);
    }
        break;
    case F_DUPFD:
    case F_DUPFD_CLOEXEC:
    case F_SETOWN:
    case F_SETSIG:
    case F_SETLEASE:
    case F_NOTIFY:
    case F_SETPIPE_SZ:
    case F_SETFD:
        {
            int arg = va_arg(va, int);
            va_end(va);
            return fcntl_f(fd, cmd, arg);
        }
        break;

        /*void*/
    case F_GETFL:
        {
            va_end(va);
            int arg =fcntl_f(fd, cmd);
            sylar::FdCtx::ptr ctx = sylar::FdMgr::GetInstance()->get(fd);
            if (!ctx || ctx->isClose() || !ctx->isScoket())
            {
                return arg;
            }
            if (ctx->getUserNoBlock())
            {
                return arg | O_NONBLOCK;
            }
            else
            {
                return arg & ~O_NONBLOCK;
            }
        }
        break;
    case F_GETOWN:
    case F_GETSIG:
    case F_GETLEASE:
    case F_GETFD:
        {
            va_end(va);
            return fcntl_f(fd, cmd);
        }
        break;
        /* struct flock* */
    case F_SETLK:
    case F_SETLKW:
    case F_GETLK:
    case F_OFD_SETLK:
    case F_OFD_SETLKW:
    case F_OFD_GETLK:
    {
        struct flock* arg = va_arg(va, struct flock*);
        va_end(va);
        return fcntl_f(fd, cmd, arg);
    }
        break;
    /* struct f_owner_ex * */

    case F_GETOWN_EX:
    case F_SETOWN_EX:
    {
        struct f_owner_ex* arg =va_arg(va, struct f_owner_ex* );
        va_end(va);
        return fcntl_f(fd, cmd, arg);
    }
        break;
    default: 
    {
        va_end(va);
        return fcntl_f(fd, cmd);
    }
        break;
    }

}



// #include <sys/ioctl.h>
int ioctl(int fd, unsigned long request, ...)
{
    va_list va;
    va_start(va, request);

    void* arg = va_arg(va, void*);
    va_end(va);

    if (FIONBIO == request)
    {
        bool user_nonblock = !!*(int*)arg;
        sylar::FdCtx::ptr ctx = sylar::FdMgr::GetInstance()->get(fd);
        if (!ctx || ctx->isClose() || !ctx->isScoket())
        {
            return ioctl_f(fd,request, arg);
        }
        ctx->setUserNoBlock(user_nonblock);
    }
    return ioctl_f(fd,request, arg);
}


int getsockopt(int sockfd, int level, int optname,
                      void *optval, socklen_t *optlen)
{
    return getsockopt_f(sockfd, level, optname, optval, optlen);
}


int setsockopt(int sockfd, int level, int optname,
                      const void *optval, socklen_t optlen)
{
    if(!sylar::t_hook_enable)
    {
        return setsockopt_f(sockfd, level, optname, optval, optlen);
    }

    if (level == SOL_SOCKET)
    {
        if (optname == SO_RCVTIMEO || optname == SO_SNDTIMEO)
        {
            sylar::FdCtx::ptr ctx = sylar::FdMgr::GetInstance()->get(sockfd);
            if (ctx)
            { 
                const timeval* tv = (const timeval*)optval;
                ctx->setTimeOut(optname, tv->tv_sec * 1000 + tv->tv_usec / 1000);
            }
        }
    }
    return setsockopt_f(sockfd, level, optname, optval, optlen);
}

}